/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE MPD front-end test

#include <boost/test/unit_test.hpp>
#include <boost/assign.hpp>
#include <mpd/client.h>
#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>

#include "../../backend/stub/Plugin.h"
#include "Plugin.h"

using namespace std;

// Convenience macros
#define REQUIRE_MPD(mpd) BOOST_REQUIRE_MESSAGE(!mpd_connection_get_error(mpd), mpd_connection_get_error_message(mpd))
#define CHECK_MPD(mpd) BOOST_CHECK_MESSAGE(!mpd_connection_get_error(mpd), mpd_connection_get_error_message(mpd))

namespace {
	// Copy and paste from MPD back-end...
	void buildTrackFromMpdSong(const mpd_song* song, Track& track)
	{
		track.setFileName(mpd_song_get_uri(song));

		int duration = mpd_song_get_duration(song);
		if(duration)
			track.setDuration(boost::posix_time::seconds(duration));

		const char* artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
		if(artist)
			track.setArtist(artist);

		const char* album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0);
		if(album)
			track.setAlbum(album);

		const char* title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
		if(title)
			track.setTitle(title);

		const char* trackNum = mpd_song_get_tag(song, MPD_TAG_TRACK, 0);
		if(trackNum) {
			string tagValue(trackNum);
			// tagValue has format "trackNumber/numTotalTracks"
			int slashPos = tagValue.find('/');
			try {
				track.setTrackNumber(boost::lexical_cast<int>(tagValue.substr(0, slashPos)));
			} catch(boost::bad_lexical_cast&) {}
		}
	}

	struct GlobalFixture {
		GlobalFixture() {
			log4cxx::BasicConfigurator::configure();
			log4cxx::Logger::getRootLogger()->setLevel(log4cxx::Level::getWarn());
		}
	};

	const Track TRACK1 = Track("filename1", boost::posix_time::seconds(10));
	const Track TRACK2 = Track("filename2", boost::posix_time::seconds(20));

	const backend::Plugin::TrackList TRACK_LIST = boost::assign::list_of(TRACK1)(TRACK2);
	const backend::Plugin::Playlist PLAYLIST = boost::assign::list_of(TRACK1)(TRACK2);
	const int VOLUME = 50;
	const backend::Plugin::PlaybackStatus PLAYBACK_STATUS = backend::Plugin::STOP;

	struct FixtureBase {
		FixtureBase()
		: b(TRACK_LIST, PLAYLIST, PLAYBACK_STATUS, VOLUME),
		  f(b),
		  bThread(boost::bind(&backend::stub::Plugin::run, &b)),
		  fThread(boost::bind(&frontend::mpd::Plugin::run, &f))
		{
		}

		~FixtureBase()
		{
			f.cancel();
			b.cancel();
			fThread.join();
			bThread.join();
		}

		backend::stub::Plugin b;
		frontend::mpd::Plugin f;
		boost::thread bThread;
		boost::thread fThread;
	};

	struct Fixture : public FixtureBase, private boost::noncopyable {
		Fixture() {
			mpd = mpd_connection_new("localhost", frontend::mpd::Plugin::DEFAULT_PORT, 1000);
			assert(mpd);
			assert(!mpd_connection_get_error(mpd));
		}

		~Fixture() {
			mpd_connection_free(mpd);
		}

		mpd_connection* mpd;
	};

	// The following can be used as a thread that runs a specific command from a second connection
	/*
	template <typename T> struct MpdFunCaller : private boost::noncopyable {
		MpdFunCaller(const boost::function<T (mpd_connection*)>& mpdFun)
		: mpdFun(mpdFun)//, started(false), done(false)
		{
			mpd = mpd_connection_new("localhost", frontend::mpd::Plugin::DEFAULT_PORT, 1000);
			BOOST_REQUIRE(mpd);
			REQUIRE_MPD(mpd);
		}

		~MpdFunCaller()
		{
			mpd_connection_free(mpd);
		}

		T operator()()
		{
			//started = true;
			//T result = mpdFun(mpd);
			//REQUIRE_MPD(mpd);
			//done = true;
			//return result;
			return mpdFun(mpd);
		}

		mpd_connection* mpd;
		boost::function<T (mpd_connection*)> mpdFun;
		//bool started;
		//bool done;
	};
	*/

	struct MpdConnection : private boost::noncopyable {
		MpdConnection()
		{
			mpd = mpd_connection_new("localhost", frontend::mpd::Plugin::DEFAULT_PORT, 1000);
			BOOST_REQUIRE(mpd);
			REQUIRE_MPD(mpd);
		}

		~MpdConnection()
		{
			mpd_connection_free(mpd);
		}

		operator mpd_connection*() const { return mpd; }

		mpd_connection* mpd;
	};
}

BOOST_GLOBAL_FIXTURE(GlobalFixture)

// First test if connection can be established
BOOST_FIXTURE_TEST_CASE(connection, FixtureBase)
{
	mpd_connection* mpd = mpd_connection_new("localhost", frontend::mpd::Plugin::DEFAULT_PORT, 1000);
	BOOST_REQUIRE(mpd);
	CHECK_MPD(mpd);
	mpd_connection_free(mpd);
}

BOOST_FIXTURE_TEST_CASE(mpd_version, Fixture)
{
	const unsigned int* version = mpd_connection_get_server_version(mpd);
	const unsigned int expected[3] = {0, 15, 0};

	BOOST_CHECK_EQUAL_COLLECTIONS(version, version+3, expected, expected+3);
}

BOOST_FIXTURE_TEST_CASE(initial_track_list, Fixture)
{
	mpd_send_list_all_meta(mpd, 0);
	REQUIRE_MPD(mpd);

	backend::Plugin::TrackList tracks;

	for(mpd_entity* entity = mpd_recv_entity(mpd); entity; entity = mpd_recv_entity(mpd)) {
		if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
			const mpd_song* song = mpd_entity_get_song(entity);
			Track track;
			buildTrackFromMpdSong(song, track);
			tracks.push_back(track);
		}

		mpd_entity_free(entity);
	}

	REQUIRE_MPD(mpd);
	BOOST_CHECK(tracks == TRACK_LIST);
}

BOOST_FIXTURE_TEST_CASE(initial_playlist, Fixture)
{
	mpd_send_list_queue_meta(mpd);
	REQUIRE_MPD(mpd);

	backend::Plugin::Playlist playlist;

	for(mpd_entity* entity = mpd_recv_entity(mpd); entity; entity = mpd_recv_entity(mpd)) {
		BOOST_REQUIRE_EQUAL(mpd_entity_get_type(entity), MPD_ENTITY_TYPE_SONG);
		const mpd_song* song = mpd_entity_get_song(entity);
		Track track;
		buildTrackFromMpdSong(song, track);
		playlist.push_back(track);

		mpd_entity_free(entity);
	}

	REQUIRE_MPD(mpd);
	BOOST_CHECK(playlist == PLAYLIST);
}

BOOST_FIXTURE_TEST_CASE(initial_status, Fixture)
{
	mpd_status* status = mpd_run_status(mpd);
	BOOST_REQUIRE(status);
	CHECK_MPD(mpd);

	BOOST_CHECK_EQUAL(mpd_status_get_volume(status), VOLUME);
	BOOST_CHECK_EQUAL(mpd_status_get_queue_length(status), PLAYLIST.size());
	BOOST_CHECK_EQUAL(mpd_status_get_song_id(status), -1);
	BOOST_CHECK_EQUAL(mpd_status_get_song_pos(status), -1);
	BOOST_CHECK_EQUAL(mpd_status_get_state(status), MPD_STATE_STOP);

	mpd_status_free(status);
}

BOOST_FIXTURE_TEST_CASE(idle_noidle_no_events, Fixture)
{
	mpd_send_idle(mpd);
	REQUIRE_MPD(mpd);
	mpd_idle result = mpd_run_noidle(mpd);
	BOOST_CHECK_EQUAL(result, 0);
	REQUIRE_MPD(mpd);
}

BOOST_FIXTURE_TEST_CASE(idle_after_play_returns_immediately, Fixture)
{
	mpd_run_play_pos(mpd, 0);
	REQUIRE_MPD(mpd);
	mpd_send_idle(mpd);
	REQUIRE_MPD(mpd);
	// Wait with timeout
	BOOST_CHECK_EQUAL(mpd_recv_idle(mpd, false), MPD_IDLE_PLAYER);
	REQUIRE_MPD(mpd);
}

BOOST_FIXTURE_TEST_CASE(play_ends_idle, Fixture)
{
	mpd_send_idle(mpd);
	REQUIRE_MPD(mpd);

	// Must send play from another connection
	MpdConnection c;
	mpd_run_play_pos(c, 0);
	REQUIRE_MPD(c);

	BOOST_CHECK_EQUAL(mpd_recv_idle(mpd, false), MPD_IDLE_PLAYER);
	REQUIRE_MPD(mpd);
}

BOOST_FIXTURE_TEST_CASE(play_plays, Fixture)
{
	mpd_run_play_pos(mpd, 0);
	REQUIRE_MPD(mpd);

	mpd_status* status = mpd_run_status(mpd);
	BOOST_REQUIRE(status);
	CHECK_MPD(mpd);

	BOOST_CHECK_EQUAL(mpd_status_get_song_pos(status), 0);

	mpd_status_free(status);
}
